/**
 * Copyright © 2012, 2013 dr. ir. Jeroen M. Valk
 * 
 * This file is part of ComPosiX.
 * 
 * ComPosiX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * ComPosiX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * ComPosiX. If not, see <http://www.gnu.org/licenses/>.
 */

var Subordinate = (function() {
	function assert(condition) {
		if (!condition) {
			throw new Error('internal error');
		}
	}

	function get(fifo, cont) {
		fifo.read(1, function(msgv) {
			if (msgv.length == 0) {
				cont(null);
			} else {
				assert(msgv.length == 1);
				cont(msgv[0]);
			}
		});
	}

	function put(fifo, msg) {
		fifo.send([ msg ]);
	}

	function result(me, you, redirect, sym, logger, name) {
		var flushed = false;
		var status = new Array();

		var counter = 0;
		var self = this;

		this.me = me;
		this.you = you;
		this.name = name;
		this.logger = logger;

		self.size = 16;
		self.unstable = false;

		function readall(i, n) {
			if (i < redirect.length) {
				assert(typeof redirect[i] == 'number');
				if (redirect[i] < 0) {
					var fifo = self.fifov[i];
					if (fifo == null) {
						readall(i + 1, n);
					} else {
						var size = fifo.size();
						if (size > 0) {
							fifo.read(size, function(msgv) {
								logger.info('ON '+i+': SEND '+size+' message(s):');
								for ( var j = 0; j < msgv.length; ++j) {
									logger.debug(msgv[j]);
									msgv[j] = [ i, msgv[j] ];
								}
								self.stdout.send(msgv);
								readall(i + 1, n + size);
							});
						} else {
							if (size < 0) {
								self.stdout.send([ i ]);
								readall(i + 1, n + 1);
							} else {
								readall(i + 1, n);
							}
						}
					}
				} else {
					readall(i + 1, n);
				}
			} else {
				continuation();
				if (n > 0) {
					self.stdout.flush();
				}
			}
		}

		function writeall(n) {
			var check = false;
			++counter;
			self.stdin.read(n, function(msgv) {
				check = true;
				var queue = new Array();
				for ( var i = 0; i < msgv.length; ++i) {
					var fd = msgv[i][0];
					if (typeof self.fifov[fd] == 'undefined') {
						self.open(fd);
					}
					if (typeof queue[fd] == 'undefined') {
						queue[fd] = new Array();
					}
					queue[fd].push(msgv[i][1]);
				}
				for ( var fd in queue) {
					assert(typeof fd == 'string');
					fd = parseInt(fd);
					assert(typeof fd == 'number');
					logger.info('ON ' + fd + ': RECV ' + queue[fd].length
							+ ' message(s):');
					for ( var i = 0; i < queue[fd].length; ++i) {
						logger.debug(queue[fd][i]);
					}
					self.write(fd, queue[fd]);
					try {
						self.flush(fd);
					} catch (e) {
						logger.error('exception in component:');
						logger.error(e);
						logger.error(e.stack);
					}
				}
				readall(0, 0);
			});
			assert(check);
			check = false;
		}

		function continuation() {
			var check = false;
			counter = 0;
			self.stdin.wait(function(n) {
				assert(check);
				check = false;
				writeall(n);
			});
			check = true;
		}

		self.stdout = new Fifo();
		self.stdout.open(512);
		self.stdin = new Fifo();
		self.stdin.open(512);

		self.load = function(name, constructor) {
			sym[name] = constructor;
		};

		self.exit = function(pid,code) {
			assert(typeof pid == 'number');
			assert(typeof code == 'number');
			assert(typeof status[pid] != 'number');
			if (status[pid] == null) {
				status[pid] = code;
			} else {
				assert(typeof status[pid] == 'function');
				var notify = status[pid];
				delete status[pid];
				notify(code);
			}
		};
		
		self.waitpid = function(pid, notify) {
			if (typeof status[pid] == 'number') {
				notify(status[pid]);
			} else {
				assert(typeof notify != 'number');
				assert(status[pid] == null);
				status[pid] = notify;
			}
		};
		
		self.exec = function(name, fd, argv) {
			var constructor = null;
			if (typeof name == 'string') {
				constructor = sym[name];
				if (typeof constructor != 'function') {
					logger.info("symbol '" + name
							+ "' not found (or not a function)");
					return null;
				}
			} else {
				if (typeof name == 'function') {
					constructor = name;
				} else {
					logger.error('internal reference not a function');
					logger.error(name);
					return null;
				}
				name = null;
			}
			if (constructor != null) {
				if (name == null) {
					logger.info('new Lambda(cpx, ' + fd + ', argv:)');
					logger.info(argv);
				} else {
					logger.info('new ' + name + '(cpx, ' + fd + ', argv:)');
					logger.info(argv);
				}
				var pid = status.length;
				status[pid] = null;
				try {
					new constructor(self, pid, fd, argv);
				} catch (e) {
					logger.error('initialization error:');
					logger.error(e);
					logger.error(e.stack);
				}
				return pid;
			}
		};

		self.initialize = function(fd) {
			self.fifov = new Array();
			self.free = new Array();
			for ( var i = 0; i < redirect.length; ++i) {
				if (typeof redirect[i] == 'number') {
					self.fifov[i] = null;
				} else {
					delete redirect[i];
					self.free.push(i);
				}
			}
			if (typeof sym.Initializer != 'undefined') {
				this.exec('Initializer');
				if (flushed) {
					this.you.send([ this.me ]);
					this.you.flush();
				}
			} else {
				assert(!flushed);
			}
		};

		self.flush = function() {
			for ( var i = 0; i < arguments.length; ++i) {
				var fd = arguments[i];
				if (typeof fd == 'number' && fd >= 0) {
					assert(fd < this.fifov.length);
					var fifo = this.fifov[fd];
					assert(typeof fifo != 'undefined');
					if (fifo == null) {
						throw new Error('cannot flush closed channel ' + fd);
					} else {
						//this.logger.info('FLUSH(' + fd + ');');
						// fifo.status(this.logger);
						fifo.flush();
					}
				} else {
					throw new Error(typeof fd);
					throw new Error('descriptor must be a positive integer: '
							+ fd);
				}
			}
			flushed = true;
		};

		self.close = function() {
			for ( var i = 0; i < arguments.length; ++i) {
				var fd = arguments[i];
				if (typeof fd == 'number' && fd >= 0) {
					assert(fd < this.fifov.length);
					var fifo = this.fifov[fd];
					assert(typeof fifo != 'undefined');
					if (fifo == null) {
						throw new Error('channel ' + fd + ' already closed');
					} else {
						this.logger.debug('CLOSING ' + fd);
						fifo.close();
					}
				} else {
					throw new Error('descriptor must be a positive integer');
				}
			}
			flushed = true;
		};

		continuation();
	}

	result.prototype.open = function() {
		if (arguments.length == 0) {
			var result;
			if (this.free.length == 0) {
				result = this.fifov.length;
				this.fifov[result] = null;
			} else {
				result = this.free.shift;
				assert(this.fifov[result] == null);
			}
			this.open(result);
			return result;
		} else {
			for ( var i = 0; i < arguments.length; ++i) {
				var fd = arguments[i];
				if (typeof fd == 'number' && fd >= 0) {
					assert(fd < this.fifov.length);
					var fifo = this.fifov[fd];
					assert(typeof fifo != 'undefined');
					if (fifo == null) {
						fifo = new Fifo();
						fifo.open(this.size);
						this.fifov[fd] = fifo;
					} else {
						fifo.open(this.size);
					}
				} else {
					throw new Error('descriptor must be a positive integer');
				}
			}
		}
	};

	result.prototype.wait = function(i, cont) {
//		this.logger.info('WAIT(' + i + ');');
		if (typeof i == 'number' && i >= 0) {
			assert(i < this.fifov.length);
			var fifo = this.fifov[i];
			assert(typeof this.fifov[i] != 'undefined');
			if (fifo == null) {
				throw new Error('waiting on closed channel ' + i);
			} else {
				if (fifo.size() == 0) {
					this.logger.debug(new Error('WAIT @ ' + i).stack);
				}
				fifo.wait(cont);
			}
		} else {
			throw new Error('descriptor must be a positive integer');
		}
	};

	result.prototype.get = function(i, cont) {
		if (typeof i == 'number' && i >= 0) {
			assert(i < this.fifov.length);
			var fifo = this.fifov[i];
			assert(typeof this.fifov[i] != 'undefined');
			if (fifo == null) {
				throw new Error('getting from closed channel ' + i);
			} else {
				if (fifo.size() == 0) {
					this.logger.debug(new Error('RD 1 @ ' + i).stack);
				}
				get(fifo, cont);
			}
		} else {
			throw new Error('descriptor must be a positive integer');
		}
	};

	result.prototype.getnow = function(i) {
//		this.logger.info('GETNOW(' + i + ');');
		if (typeof i == 'number' && i >= 0) {
			assert(i < this.fifov.length);
			var fifo = this.fifov[i];
			assert(typeof this.fifov[i] != 'undefined');
			if (fifo == null) {
				throw new Error('getting from closed channel ' + i);
			} else {
				var result;
				this.logger.info(fifo.size());
//				fifo.status(this.logger);
				get(fifo, function(msg) {
					assert(typeof msg != 'undefined');
					result = msg;
				});
				assert(typeof result != 'undefined');
				return result;
			}
		} else {
			throw new Error('descriptor must be a positive integer');
		}
	};

	result.prototype.read = function(i, n, cont) {
		if (typeof i == 'number' && i >= 0) {
			assert(i < this.fifov.length);
			var fifo = this.fifov[i];
			assert(typeof this.fifov[i] != 'undefined');
			if (fifo == null) {
				throw new Error('reading from closed channel ' + i);
			} else {
				if (fifo.size() < n) {
					this.logger.debug(new Error('RD ' + n + ' @ ' + i).stack);
				}
				fifo.read(n, cont);
			}
		} else {
			throw new Error('descriptor must be a positive integer');
		}
	};

	result.prototype.recv = function(i, n) {
		var result = null;
		this.read(i, n, function(msgv) {
			result = msgv;
		});
		assert(result != null);
		return result;
	};
	
	result.prototype.put = function(i, msg) {
		if (typeof i == 'number' && i >= 0) {
			assert(i < this.fifov.length);
			var fifo = this.fifov[i];
			assert(typeof this.fifov[i] != 'undefined');
			if (fifo == null) {
				throw new Error('putting to closed channel ' + i);
			} else {
				put(fifo, msg);
			}
		} else {
			throw new Error('descriptor must be a positive integer');
		}
	};

	result.prototype.write = function(i, msgv) {
		if (typeof i == 'number' && i >= 0) {
			assert(i < this.fifov.length);
			var fifo = this.fifov[i];
			assert(typeof this.fifov[i] != 'undefined');
			if (fifo == null) {
				throw new Error('writing to closed channel ' + i);
			} else {
				fifo.send(msgv);
			}
		} else {
			throw new Error('descriptor must be a positive integer');
		}
	};

	return result;
})();
